--------------------------------------------------------------------------------
-- Company: Synapse
-- Module Name:    Control - Behavioral
--------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Control is
    Port (     
        clk               : in std_logic;
        opcode            : in std_logic_vector(7 downto 0);
                
        reset             : in std_logic;
        conditionCode     : in std_logic_vector(3 downto 0);
        immediate         : in std_logic_vector(7 downto 0);
        registerBData     : in std_logic_vector(1 downto 0);

        flagF             : in    std_logic;
        flagC             : in    std_logic;
        flagL             : in    std_logic;
        flagN             : in    std_logic;
        flagZ             : in    std_logic;
                     
        RegWrite_EN       : out std_logic;
        RegRead_EN        : out std_logic;
        RegAddrMux        : out std_logic;
        registerAOut      : out std_logic_vector(15 downto 0);
        registerBOut      : out std_logic_vector(15 downto 0);
        Q                 : in std_logic_vector(15 downto 0);   
                           
                                
        memoryRead        : out std_logic;
        memoryWrite       : out std_logic;
        instructionWrite  : out std_logic;
        aluControl        : out std_logic_vector(2 downto 0);
        aluBInputMux      : out std_logic_vector(1 downto 0);
        regAInputMux      : out std_logic_vector(1 downto 0);
        memoryAddressMux  : out std_logic;
        addToPC           : out std_logic_vector(7 downto 0);
        nextPCSelectMux   : out std_logic;
        memPage           : out std_logic_vector(1 downto 0)           
    );              
end Control;

architecture Behavioral of Control is

    type CPUStates is (S_FETCH, S_DECODE, S_REG_READ,S_ALU, S_REG_WRITE, S_MEM_ACCESS, S_JUMP);
    signal state : CPUStates;

    signal nextPCSelectMuxSig : std_logic;
        signal regAOut : std_logic_vector( 15 downto 0);
        signal regBOut : std_logic_vector( 15 downto 0);

begin
        registerAOut <= regAOut;
        registerBOut <= regBOut;
    nextPCSelectMux <= nextPCSelectMuxSig;
    process (clk, reset)
    begin

        if (reset = '0') then
            state <= S_FETCH;
            RegWrite_EN <= '0';
                        RegRead_EN <= '0';
            memoryRead <= '1';
            memoryWrite <= '1';
            instructionWrite <= '0';
            aluControl <= "111";
            aluBInputMux <= "00";
            regAInputMux <= "00";
            memoryAddressMux <= '1';
            addToPC <= (others => '0');
            nextPCSelectMuxSig <= '1';
                        memPage <= "00";
                        RegAddrMux <= '0';
              
                        regAOut <= ( others => '0');
                        regBOut <= ( others => '0');                  
                             
        elsif (rising_edge(clk)) then
            
            if (state = S_JUMP) then
                if (nextPCSelectMuxSig = '0') then -- jump was taken
                    state <= S_DECODE;
                    nextPCSelectMuxSig <= '1';
                    memoryRead <= '0';
                else -- jump wasn't taken
                    state <= S_FETCH;
                end if;
                                                     
            else
                -- Set the defaults and then this is overriden below.
                    RegRead_EN <= '0';
                                RegWrite_EN <= '0';
                memoryRead <= '1';
                memoryWrite <= '1';
                instructionWrite <= '0';
                aluBInputMux <= "00";
                regAInputMux <= "00";
                memoryAddressMux <= '1';
                addToPC <= (others => '0');
                nextPCSelectMuxSig <= '1';
                aluControl <= "111";
                                RegAddrMux <= '0';
                                regAOut <= regAOut;
                                regBOut <= regBOut;

                                
                if (state = S_FETCH) then
                    state <= S_DECODE;
                    memoryRead <= '0';
                    addToPC <= "00000001";
                                        memoryAddressMux <= '1';
                                        instructionWrite <='1';
                                       
                elsif (state = S_DECODE) then
                                        RegRead_EN <= '1';
                                        state <= S_REG_READ;
                                       
                                        
                                elsif (state = S_REG_READ) and  ((opcode(7 downto 4)= "0000") or (opcode(7 downto 4)="0110") or (opcode(7 downto 4)= "0100")) then
                                        RegAddrMux <= '1';
                                        RegRead_EN <= '1';
                                        state <= S_ALU;
                                        regAOut <= Q;
                                else
                                               
                -- If in decode state and finished is high, instruction is read.
                      case opcode(7 downto 4) is
                                           when "0110" =>
                                                        regBOut <= Q;
                                if (conditionCode = "0000") then --smpi: 0110 0000 immediatehigh immediatelow

                                                                state <= S_FETCH;
                                memPage <= immediate(1 downto 0);
                            elsif (conditionCode = "0001") then --smp: 0110 0001 0000 Rsrc
                                state <= S_FETCH;
                                memPage <= regBOut (1 downto 0);
                            elsif (conditionCode = "0010") then --gmp: 0110 0010 0000 Rsrc
                                                                state <= S_FETCH;
                                RegWrite_EN <= '1';
                                                                RegAddrMux <='1';
                            end if;
                                          when "0101"  => --addi
                                                        regAOut <= Q;
                                                        state <= S_FETCH;
                                                        RegWrite_EN <= '1';
                            regAInputMux <= "01";
                            aluControl <= "000";
                            aluBInputMux <= "10";
                      when "1001"  => --subi
                                                        regAOut <= Q;
                                                        state <= S_FETCH;
                            RegWrite_EN <= '1';
                                                        regAInputMux <= "01";
                            aluControl <= "001";
                            aluBInputMux <= "10";
                        when "1011"  => --cmpi
                            state <= S_FETCH;
                                                        regAOut <= Q; 
                            RegWrite_EN <= '0';
                            aluControl <= "101";
                            aluBInputMux <= "10";
                        when "0001"  => --andi
                            state <= S_FETCH;
                                                        regAOut <= Q;
                            RegWrite_EN <= '1';
                                                        regAInputMux <= "01";
                            aluControl <= "010";
                            aluBInputMux <= "11";                    
                        when "0010"  => --ori
                                state <= S_FETCH;
                            RegWrite_EN <= '1';
                                                         regAInputMux <= "01";
                                                        regAOut <= Q;
                            aluControl <= "011";
                            aluBInputMux <= "11";
                        when "0011"  => --xori
                            state <= S_FETCH;
                                RegWrite_EN <= '1';
                                                        regAOut <= Q;
                                                        regAInputMux <= "01";
                            aluControl <= "100";
                            aluBInputMux <= "11";
                        when "1101"  => --movi
                            state <= S_FETCH;
                                                        regAOut <= Q;
                            RegWrite_EN <= '1';
                                                        regAInputMux <= "01";
                            aluControl <= "111";
                            aluBInputMux <= "11";
                        when "1000"  => --lshOrLshi
                            state <= S_FETCH;
                                                        regAOut <= Q;
                            RegWrite_EN <= '1';
                                                        regAInputMux <= "01";
                            aluControl <= "110";
                            if (opcode(2) = '1') then --lsh
                                aluBInputMux <= "00";
                            else --Lshi
                                aluBInputMux <= "11";
                            end if;                
                        when "1111"  => --lui
                                                                regAOut <= Q;
                                    state <= S_FETCH;
                                regAInputMux <= "10";
                                RegWrite_EN <= '1';
                                                when "0100"  => --loadStorJcondJal
                            case opcode(3 downto 0) is
                                when "0000" => --load
                                    if (state = S_ALU) then
                                        state <= S_MEM_ACCESS;
                                        memoryRead <= '0';
                                        memoryAddressMux <= '0';
                                                                                regBOut <= Q;
                                                    
                                    elsif (state = S_MEM_ACCESS) then
                                                                                        memoryAddressMux <= '1';
                                                state <= S_FETCH;
                                            regAInputMux <= "00";
                                            RegWrite_EN <= '1';
                                                                                        
                                                                                      
                                                                        end if;

                                when "0100" =>    --stor
                                    if (state = S_ALU) then
                                        state <= S_MEM_ACCESS;
                                        memoryAddressMux <= '0';
                                        memoryWrite <= '0';
                                                                                regBOut <= Q;
                                    elsif (state = S_MEM_ACCESS) then
                                              state <= S_FETCH;
                                                                                memoryAddressMux <= '1'; 
                                    end if;

                                when "1100" =>    --jcond
                                    state <= S_JUMP;
                                                                        regBOut <= Q;
                                    case conditionCode is
                                        when "0000" =>--zero
                                            if (flagZ = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0001" =>--!zero
                                            if (flagZ = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1101" =>--negative || zero
                                            if (flagZ = '1' or flagN = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0010" =>--carry
                                            if (flagC = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;    
                                        when "0011" =>--!carry
                                            if (flagC = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0100" =>--less
                                            if (flagL = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0101" =>--!less
                                            if (flagL = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1010" =>--!less && !zero
                                            if (flagL = '0' and flagZ = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1011" =>--less || zero
                                            if (flagL = '1' or flagZ = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0110" =>--negative
                                            if (flagN = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "0111" =>--!negative
                                            if (flagN = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1000" =>--overflow
                                            if (flagF = '1') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1001" =>--!overflow
                                            if (flagF = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1100" =>--!negative  && !zero
                                            if (flagN = '0' and flagZ = '0') then
                                                nextPCSelectMuxSig <= '0';
                                            end if;
                                        when "1110" =>--true
                                            nextPCSelectMuxSig <= '0';
                                        when "1111" =>--false
                                            state <= S_FETCH;
                                        when others =>
                                            null;
                                    end case;
                                when "1000" => --jal
                                    if (state = S_ALU) then
                                                                                regBOut <= Q;
                                                                                state <= S_REG_WRITE;
                                        regAInputMux <= "11";
                                        RegWrite_EN <= '1';
                                    elsif (state = S_REG_WRITE) then
                                        state <= S_JUMP;
                                        nextPCSelectMuxSig <= '0';                                        
                                    end if;
                                when others =>
                                    null;
                            end case;
                        when "1100"  => --bcond
                            state <= S_DECODE;
                            addToPC <= "00000001";
                            nextPCSelectMuxSig <= '1';
                            memoryAddressMux <= '1';
                            memoryRead <= '0';
                                                        regAOut <= Q; 
                            case conditionCode is
                                when "0000" =>--zero
                                    if (flagZ = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "0001" =>--!zero
                                    if (flagZ = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1101" =>--negative || zero
                                    if (flagN = '1' or flagZ = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "0010" =>--carry
                                    if (flagC = '1') then
                                        addToPC <= immediate;
                                    end if;    
                                when "0011" =>--!carry
                                    if (flagC = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "0100" =>--less
                                    if (flagL = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "0101" =>--!less
                                    if (flagL = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1010" =>--!less && !zero
                                    if (flagL = '0' and flagZ = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1011" =>--less && zero
                                    if (flagL = '1' or flagZ = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "0110" =>--negative
                                    if (flagN = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "0111" =>--!negative
                                    if (flagN = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1000" =>--overflow
                                    if (flagF = '1') then
                                        addToPC <= immediate;
                                    end if;
                                when "1001" =>--!overflow
                                    if (flagF = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1100" =>--!negative  && !zero
                                    if (flagN = '0' and flagZ = '0') then
                                        addToPC <= immediate;
                                    end if;
                                when "1110" =>--true
                                    addToPC <= immediate;
                                when "1111" =>--false
                                    addToPC <= "00000001";
                                when others =>
                                    null;
                            end case;
                                
                        when "0000"  => -- ALU operation or mov
                            if (opcode(3 downto 0) = "0000") then -- blank instruction, do nothing forever
                                null;

                            else
                                                                regBOut <= Q; 
                                    state <= S_FETCH;
                                RegWrite_EN <= '1';
                                regAInputMux <= "01";
                                aluBInputMux <= "00";
                                case opcode(3 downto 0) is
                                    when "0101" =>--add                                     
                                        aluControl <= "000";
                                    when "1001" =>--sub                                     
                                        aluControl <= "001";
                                    when "0001" =>--and
                                        aluControl <= "010";
                                    when "0010" =>--or                                     
                                        aluControl <= "011";
                                    when "0011" =>--xor                                     
                                        aluControl <= "100";
                                    when "1011" =>--cmp
                                        RegWrite_EN <= '0';
                                        aluControl <= "101";
                                    when "1101" =>--mov                                     
                                        aluControl <= "111";
                                    when others =>
                                        null;
                                end case;
                            end if;
                      when others =>
                            null;
                    end case;
                                    
                          end if;
                          

            end if;

        end if;
    
    end process;

end Behavioral;
